package com.chuangke.file.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.chuangke.common.constant.ElasticIndexConstants;
import com.chuangke.common.constant.FileConstants;
import com.chuangke.common.constant.Masks;
import com.chuangke.common.event.file.FileUpdateEvent;
import com.chuangke.common.exception.ChuangkeException;
import com.chuangke.common.idgen.IdManager;
import com.chuangke.common.utils.SecurityUtils;
import com.chuangke.elasticsearch.service.ElasticsearchService;
import com.chuangke.file.dao.FileInfoMapper;
import com.chuangke.file.dao.FileVersionMapper;
import com.chuangke.file.domain.FileDeleteDTO;
import com.chuangke.file.domain.FileRestoreDTO;
import com.chuangke.file.domain.PermissionSaveDTO;
import com.chuangke.file.model.FileInfo;
import com.chuangke.file.model.FileInfoAffix;
import com.chuangke.file.model.FileInfoDeleted;
import com.chuangke.file.model.FileInfoVersion;
import com.chuangke.file.service.*;
import com.chuangke.system.entity.SysComment;
import com.chuangke.system.entity.SysFocus;
import com.chuangke.system.enums.LogBisType;
import com.chuangke.system.event.CommentUpdateEvent;
import com.chuangke.system.event.OptLogUpdateEvent;
import com.chuangke.system.service.SysCommentService;
import com.chuangke.system.service.SysFocusService;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @Description
 * @author stm
 * @date 2019年2月24日 下午9:52:09
 */
@Service
public class FileInfoServiceImpl extends ServiceImpl<FileInfoMapper, FileInfo> implements FileInfoService {

	private static final Logger logger = LoggerFactory.getLogger(FileInfoServiceImpl.class);

	@Autowired
	private ApplicationEventPublisher eventPublisher;

	@Autowired
	private SysCommentService sysCommentService;
	@Autowired
	private SysFocusService sysFocusService;
	@Autowired
	private FileInfoAffixService fileInfoAffixService;
	@Autowired
	private FileQueryService fileQueryService;
	@Autowired
	private FileDbankService fileDbankService;
//	@Autowired
//	private ElasticsearchService elasticsearchService;
	@Autowired
	private FileInfoDeletedService fileInfoDeleteService;
	@Autowired
	private SysFilePermissionService sysFilePermissionService;

	@Autowired
	private FileVersionMapper fileVersionMapper;
	
	@Override
	public void saveFileInfo(FileInfo fileInfo) {
		baseMapper.insert(fileInfo) ;
	}

	@Override
	public void deleteFileInfo(String fileId) {
		baseMapper.deleteById(fileId) ;
	}
	
	@Override
	public FileInfo findFileInfo(String fileId) {
		return baseMapper.selectById(fileId) ;
	}
	
	@Override
	public List<FileInfo> findFileInfos(List<String> fileIds) {
		return baseMapper.selectBatchIds(fileIds) ;
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public void move(String dbankId, List<String> idList, String parentId) {
		checkCanMoveOrCopy(dbankId, idList, parentId, "移动");

		List<FileInfo> fileList = baseMapper.selectBatchIds(idList);
		fileList.forEach(item -> item.setParentId(parentId));
		saveOrUpdateBatch(fileList);
		this.update(
				new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getRemarks, parentId).in(FileInfo::getId, idList));
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public void move(String sourceDbankId, List<String> sourceFileIds, String targetDbankId, String targetParentId) {
		List<FileInfo> fileList = baseMapper.selectBatchIds(sourceFileIds);
		fileList.forEach(item -> {
			item.setDbankId(targetDbankId);
			item.setParentId(targetParentId);
		});
		this.saveOrUpdateBatch(fileList);
	}

    @Override
	@Transactional(rollbackFor = Throwable.class)
	public void copy(String dbankId, List<String> idList, String parentId) {
		checkCanMoveOrCopy(dbankId, idList, parentId, "复制");
		List<FileInfo> fileList = baseMapper.selectBatchIds(idList);
		fileList.forEach(item -> {
			item.setParentId(parentId);
			item.setId(IdManager.nextId());
		});
		saveBatch(fileList);
	}

	private void checkCanMoveOrCopy(String dbankId, List<String> idList, String parentId, String actionName) {
		if (FileConstants.FILE_ROOT_PARENT.equals(parentId)) {
			return;
		}

		FileInfo parentFile = fileQueryService.load(parentId);

		if (!FileInfo.TYPE_DIR.equals(parentFile.getType())) {
			throw new ChuangkeException(String.format("未找到%s到的目录", actionName));
		}

		List<FileInfo> deepFiles = fileQueryService.listDeepFiles(idList);
		List<String> deepDirs = deepFiles.stream().filter(file -> FileInfo.TYPE_DIR.equals(file.getType()))
				.map(FileInfo::getId).collect(Collectors.toList());
		if (deepDirs.contains(parentId)) {
			throw new ChuangkeException(String.format("不能将文件%s到自身或其子目录下", actionName));
		}
	}

	@Override
	public FileInfo addRemarks(String id, String remarks) {
		FileInfo fileInfo = fileQueryService.load(id);

		this.update(new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getRemarks, remarks).eq(FileInfo::getId, id));

		eventPublisher.publishEvent(new FileUpdateEvent(this, Collections.singletonList(id)));

		fileInfo.setRemarks(remarks);
		return fileInfo;
	}

	@Override
	public FileInfo addComments(String fileId, String fileName, String comments) {
		FileInfo file = fileQueryService.load(fileId);

		SysComment sysComment = new SysComment();
		sysComment.setTopicType(SysComment.TOPIC_TYPE_FILE);
		sysComment.setTopicId(fileId);
		sysComment.setTopicName(fileName);
		sysComment.setContent(comments);
		sysComment.setIsReply("0");
		sysComment.setReplyId("0");
		sysComment.setReplyUser("0");
		sysCommentService.save(sysComment);

		return file;
	}

	@Override
	public void rename(FileInfo file) {
		this.update(new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getName, file.getName()).eq(FileInfo::getId,
				file.getId()));
		fileVersionMapper.update(null, new UpdateWrapper<FileInfoVersion>().lambda().set(FileInfoVersion::getName, file.getName())
				.eq(FileInfoVersion::getId,file.getId()).eq(FileInfoVersion::getSignId,file.getSignId()));
		
		eventPublisher.publishEvent(new FileUpdateEvent(this, Collections.singletonList(file.getId())));
		
		eventPublisher.publishEvent(new OptLogUpdateEvent(this, LogBisType.file.name(),file.getId(),file.getName()));
		eventPublisher.publishEvent(new CommentUpdateEvent(this, SysComment.TOPIC_TYPE_FILE,file.getId(),file.getName()));
	}

	@Override
	public FileInfo createDir(FileInfo file) {
		if (file.getParentId() == null) {
			file.setParentId(FileConstants.FILE_ROOT_PARENT);
		}
		file.setType(FileInfo.TYPE_DIR);
		file.setId(IdManager.nextId());
		file.setIsPublish(Masks.BOOL_NO);
		baseMapper.insert(file);
		return file;
	}

	@Override
	public FileInfo focus(String fileId) {
		String userid = SecurityUtils.getUserId();
		SysFocus sysFocus = new SysFocus();
		sysFocus.setUserId(userid);
		sysFocus.setCreatedAt(new Date());
		sysFocus.setCreatedBy(userid);
		sysFocus.setPropType(SysFocus.PROP_TYPE_FILE);
		sysFocus.setPropId(fileId);
		sysFocusService.focus(sysFocus);

		return fileQueryService.load(fileId);
	}

	@Override
	public FileInfo unFocus(List<String> fileIds) {
		if (CollectionUtils.isEmpty(fileIds)) {
			return null;
		}
		String userId = SecurityUtils.getUserId();
		fileIds.forEach(fileId -> {
			sysFocusService.cancel(userId, fileId);
			fileQueryService.load(fileId);
		});
		return null;
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public FileInfo addAffix(String fileId, List<FileInfoAffix> affixs) {
		affixs.forEach(affix -> affix.setId(IdManager.nextId()));
		fileInfoAffixService.saveBatch(affixs);
		return fileQueryService.load(fileId);
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public FileInfo removeAffix(String fileId, List<FileInfoAffix> affixs) {
		fileInfoAffixService.removeByIds(affixs.stream().map(FileInfoAffix::getId).collect(Collectors.toList()));
		return fileQueryService.load(fileId);
	}

	@Override
	public void updateHandleInfo(FileInfo file) {
		LambdaUpdateWrapper<FileInfo> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
		lambdaUpdateWrapper.eq(FileInfo::getId, file.getId()).set(FileInfo::getThumStatus, file.getThumStatus())
				.set(FileInfo::getThumStorage1, file.getThumStorage1())
				.set(FileInfo::getThumStorage2, file.getThumStorage2())
				.set(FileInfo::getIndexStatus, file.getIndexStatus());

		baseMapper.update(null, lambdaUpdateWrapper);
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public List<FileInfo> deleteToRecycle(FileDeleteDTO param) {
		List<FileInfo> fileList = baseMapper.selectList(new QueryWrapper<FileInfo>().lambda()
				.eq(FileInfo::getDbankId, param.getDbankId()).in(FileInfo::getId, param.getFiles()));
		List<String> delList = fileList.stream().map(FileInfo::getId).collect(Collectors.toList());
		baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getDelFlag, Masks.BOOL_YES)
				.eq(FileInfo::getDbankId, param.getDbankId()).in(FileInfo::getId, delList));
		return fileList;
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public List<FileInfo> restoreFromRecycle(FileRestoreDTO param) {
		List<FileInfo> fileList = baseMapper.selectList(new QueryWrapper<FileInfo>().lambda()
				.eq(FileInfo::getDbankId, param.getDbankId()).in(FileInfo::getId, param.getFiles()));
		// 如果被删除的文件的父级目录被删除了，还原的时候还原到根目录
		// 如果被删除的文件的父级目录没有被删除，还原的时候还原到原目录下面
		List<FileInfo> parentDeletedList = new ArrayList<>();
		List<FileInfo> parentUnDeletedList = new ArrayList<>();
		fileList.forEach(file -> {
			List<FileInfo> parentFiles = fileQueryService.listAllParentFiles(file.getId());
			boolean isDeleted = parentFiles.stream().anyMatch(f -> Masks.BOOL_YES.equals(f.getDelFlag()));
			if (isDeleted) {
				parentDeletedList.add(file);
			} else {
				parentUnDeletedList.add(file);
			}
		});

		if (CollectionUtils.isNotEmpty(parentDeletedList)) {
			List<String> delList = parentDeletedList.stream().map(FileInfo::getId).collect(Collectors.toList());
			baseMapper.update(null,
					new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getParentId, "0")
							.set(FileInfo::getDelFlag, Masks.BOOL_NO).eq(FileInfo::getDbankId, param.getDbankId())
							.in(FileInfo::getId, delList));
		}
		if (CollectionUtils.isNotEmpty(parentUnDeletedList)) {
			List<String> unDelList = parentUnDeletedList.stream().map(FileInfo::getId).collect(Collectors.toList());
			baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getDelFlag, Masks.BOOL_NO)
					.eq(FileInfo::getDbankId, param.getDbankId()).in(FileInfo::getId, unDelList));
		}
		return fileList;

	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public List<FileInfo> deleteShift(FileDeleteDTO param) {
		List<FileInfo> fileList = baseMapper.selectList(new QueryWrapper<FileInfo>().lambda()
				.eq(FileInfo::getDbankId, param.getDbankId()).in(FileInfo::getId, param.getFiles()));

		doDeleteShift(fileList);

		return fileList;
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public List<FileInfo> cleanRecycle(String dbankId) {
		List<FileInfo> fileList = baseMapper.selectList(new QueryWrapper<FileInfo>().lambda()
				.eq(FileInfo::getDbankId, dbankId).eq(FileInfo::getDelFlag, Masks.BOOL_YES));

		doDeleteShift(fileList);

		return fileList;
	}

	private void doDeleteShift(List<FileInfo> fileList) {
		if (CollectionUtils.isEmpty(fileList)) {
			return;
		}

		fileList = fileQueryService.listDeepFiles(fileList.stream().map(FileInfo::getId).collect(Collectors.toList()));

		List<String> delList = fileList.stream().map(FileInfo::getId).collect(Collectors.toList());

		long releaseSpace = fileList.stream().mapToLong(FileInfo::getSize).sum();

		if (!CollectionUtils.isEmpty(delList)) {
			baseMapper.deleteBatchIds(delList);
			fileDbankService.addUsedSpace(fileList.get(0).getDbankId(), BigInteger.valueOf(releaseSpace * -1));
		}

		List<FileInfo> fileTypeList = fileList.stream().filter(file -> FileInfo.TYPE_FILE.equals(file.getType()))
				.collect(Collectors.toList());
		if (!CollectionUtils.isEmpty(fileTypeList)) {
			List<FileInfoDeleted> list = new ArrayList<>(fileTypeList.size());
			fileTypeList.forEach(file -> {
				FileInfoDeleted deleteFile = new FileInfoDeleted();
				try {
					org.apache.commons.beanutils.BeanUtils.copyProperties(deleteFile, file);
				} catch (Exception e) {
					logger.error(e.getMessage());
				}
				list.add(deleteFile);
			});

			fileInfoDeleteService.saveBatch(list);
//			elasticsearchService.deleteBatch(ElasticIndexConstants.FILE_INDEX,
//					fileTypeList.stream().map(FileInfo::getId).collect(Collectors.toList()));
		}
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public void share(PermissionSaveDTO dto) {
		if (CollectionUtils.isEmpty(dto.getFiles())) {
			return;
		}

		sysFilePermissionService.savePermission(dto);
		baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getIsShared, Masks.BOOL_YES)
				.in(FileInfo::getId, dto.getFiles()));
	}

	@Override
	@Transactional(rollbackFor = Throwable.class)
	public void unShare(String dbankId, List<String> fileIds) {
		sysFilePermissionService.removeByFiles(fileIds);
		baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getIsShared, Masks.BOOL_NO)
				.in(FileInfo::getId, fileIds));
	}

	@Override
	public void category(String dbankId, List<String> fileIds, String categoryId) {
		baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getCategory, categoryId)
				.in(FileInfo::getId, fileIds));
	}

	@Override
	public void publish(String dbankId, List<String> fileIds) {
		baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getIsPublish, Masks.BOOL_YES)
				.in(FileInfo::getId, fileIds));
		eventPublisher.publishEvent(new FileUpdateEvent(this, fileIds));
	}

	@Override
	public void unPublish(String dbankId, List<String> fileIds) {
		baseMapper.update(null, new UpdateWrapper<FileInfo>().lambda().set(FileInfo::getIsPublish, Masks.BOOL_NO)
				.in(FileInfo::getId, fileIds));
		eventPublisher.publishEvent(new FileUpdateEvent(this, fileIds));
	}

}
